package com.studiofortress.sf.structure.intersection; 

import com.studiofortress.sf.structure.Actor;
import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.Rectangle2D;
import com.studiofortress.sf.util.structure.ImageActor;
import com.studiofortress.sf.graphics.GLTexture;

/**
 * The Intersection class is for stating how an Actor
 * should be represented when it is checked for an
 * Intersection. This allows an Actor to be represented
 * however it wishes to be, regardless of how other
 * Actors are represented.
 * 
 * For example one Actor might have a complex image and
 * so wishes to be represented through the ImageIntersection
 * class. Another Actor might wish to be represented as
 * a single pixel (such as a bullet in a game). Both of
 * these types of representation will be respected during
 * the checking.
 * 
 * Internally this also sets out some default methods
 * for different types of collisions that the subclasses
 * of this Intersection must conform too.
 * @author Joseph Lenton
 * @version 08/08/2008
 */
 public abstract class Intersection
 {
    // the directions x and y will be incrimented.
    // must be in the order, right, down, left, up,
    // as it corresponds to the order the pixels are checked
    private static final int MOVE_RIGHT = 0;
    private static final int MOVE_DOWN = 1;
    private static final int MOVE_LEFT = 2;
    private static final int MOVE_UP = 3;
     
    /**
     *
     */
    static boolean intersectionPixelToImage(Actor thisActor, ImageActor otherActor)
    {
        int thisX = (int)thisActor.getX();
        int thisY = (int)thisActor.getY();
        
        int otherX = (int)otherActor.getX()-otherActor.getWidth()/2;
        int otherY = (int)otherActor.getY()-otherActor.getHeight()/2;
        
        // find the maximum and minimum values of the area which is overlapping
        int xMin = Math.max(thisX, otherX);
        int xMax = Math.min(thisX+1, otherX + otherActor.getWidth());

        int yMin = Math.max(thisY, otherY);
        int yMax = Math.min(thisY+1, otherY + otherActor.getHeight());
        
        // first ensure values of either actors are actually colliding
        if (yMin >= yMax || xMin >= xMax) {
            return false;
        }
        
        // then check the alpha
        Color color = new Color(otherActor.getImage().getRGB(xMin-otherX, yMin-otherY));
        return color.getAlpha() > 0;
    }
     
    /**
     *
     */
    static boolean intersectionPixelToBounds(Actor thisActor, Actor otherActor)
    {
        int thisX = (int)thisActor.getX();
        int thisY = (int)thisActor.getY();
        
        int otherX = (int)otherActor.getX();
        int otherY = (int)otherActor.getY();
        int halfWidth = otherActor.getWidth()/2;
        int halfHeight = otherActor.getHeight()/2;
        
        return otherX-halfWidth <= thisX && thisX <= otherX+halfWidth &&
                otherY-halfHeight <= thisY && thisY <= otherY+halfHeight;
    }

    /**
     *
     */
    static boolean intersectionPixelToShape(Actor thisActor, Actor otherActor, Shape otherShape)
    {
        return otherShape.contains(otherActor.getX()-thisActor.getX(),
            otherActor.getY()-thisActor.getY());
    }
    
    /**
      *
      */
    static boolean intersectionBoundsToBounds(Actor thisActor, Actor otherActor)
    {
        int thisWidth = thisActor.getWidth();
        int thisHeight = thisActor.getHeight();
        
        int thisX = (int)thisActor.getX() - thisWidth/2;
        int thisY = (int)thisActor.getY() - thisHeight/2;
        
        int otherWidth = otherActor.getWidth();
        int otherHeight = otherActor.getHeight();
        
        int otherX = (int)otherActor.getX() - otherWidth/2;
        int otherY = (int)otherActor.getY() - otherHeight/2;
        
        // find the maximum and minimum values of the area which is overlapping
        int xMin = Math.max(thisX , otherX);
        int xMax = Math.min(thisX + thisWidth, otherX + otherWidth);

        int yMin = Math.max(thisY, otherY);
        int yMax = Math.min(thisY + thisHeight, otherY + otherHeight);
        
        return !(yMin >= yMax || xMin >= xMax);
    }
    
    /**
      *
      */
    static boolean intersectionBoundsToImage(Actor thisActor, ImageActor otherActor)
    {
        int thisWidth = thisActor.getWidth();
        int thisHeight = thisActor.getHeight();
        
        int thisX = (int)thisActor.getX() - thisWidth/2;
        int thisY = (int)thisActor.getY() - thisHeight/2;
        
        int otherWidth = otherActor.getWidth();
        int otherHeight = otherActor.getHeight();
        
        int otherX = (int)otherActor.getX() - otherWidth/2;
        int otherY = (int)otherActor.getY() - otherHeight/2;
        
        // find the maximum and minimum values of the area which is overlapping
        int xMin = Math.max(thisX , otherX);
        int xMax = Math.min(thisX + thisWidth, otherX + otherWidth);

        int yMin = Math.max(thisY, otherY);
        int yMax = Math.min(thisY + thisHeight, otherY + otherHeight);
        
        // first ensure values of either actors are actually colliding
        if (yMin >= yMax || xMin >= xMax)
            return false;
        
        // get both actors images
        GLTexture img = otherActor.getImage();
        
        // for counting how many pixels we've checked
        int pixelCount = 0;
        int totalPixelCount = (xMax-xMin)*(yMax-yMin);
        
        // start iterating at the beginning of bounding area,
        int x = xMin;
        int y = yMin;
        
        // incriment yMin to take into account starting on first line,
        // otherwise it will check it a second time
        yMin++;
        
        // the direction the pixels are being checked in,
        // where x or y will increase of decrese
        int direction = 0;
        
        // start iteration
        while (true) {
            // change x and y accordin to which direction were moving
            if (direction == MOVE_RIGHT) {
                x++;
                if (x == xMax) {
                    x--;
                    xMax--;
                    direction = MOVE_DOWN;
                }
            } else if (direction == MOVE_DOWN) {
                y++;
                if (y == yMax) {
                    y--;
                    yMax--;
                    direction = MOVE_LEFT;
                }
            } else if (direction == MOVE_LEFT) {
                x--;
                if (x == xMin) {
                    xMin++;
                    direction = MOVE_UP;
                }
            } else if (direction == MOVE_UP) {
                y--;
                if (y == yMin) {
                    yMin++;
                    direction = MOVE_RIGHT;
                }
            }

            // incriment that were checking another pixel
            pixelCount++;

            // check if pixels at same point on both actors are not transparent
            if (img.getRGB(x-otherX, y-otherY) != 0) {
                return true;
            // if we've looked at all pixels
            } else if (pixelCount >= totalPixelCount) {
                return false;
            }
        }
    }
    
    /**
      *
      */
    static boolean intersectionBoundsToShape(Actor thisActor, Actor otherActor, Shape otherShape)
    {
        return otherShape.intersects(
                new Rectangle2D.Float(
                    thisActor.getX() - otherActor.getX() - thisActor.getWidth()/2,
                    thisActor.getY() - otherActor.getY() - thisActor.getHeight()/2,
                    thisActor.getWidth(), thisActor.getHeight()));
    }
    
    /**
      *
      */
     static boolean intersectionImageToImage(ImageActor thisActor, ImageActor otherActor)
     {
        // store positions converted from centre to top-left corner
        int thisX = (int)thisActor.getX()-thisActor.getWidth()/2;
        int thisY = (int)thisActor.getY()-thisActor.getHeight()/2;
        
        int otherX = (int)otherActor.getX()-otherActor.getWidth()/2;
        int otherY = (int)otherActor.getY()-otherActor.getHeight()/2;
        
        // find the maximum and minimum values of the area which is overlapping
        int xMin = Math.max(thisX , otherX);
        int xMax = Math.min(thisX + thisActor.getWidth(), otherX + otherActor.getWidth());

        int yMin = Math.max(thisY, otherY);
        int yMax = Math.min(thisY + thisActor.getHeight(), otherY + otherActor.getHeight());
        
        // first ensure values of either actors are actually colliding
        if (yMin >= yMax || xMin >= xMax)
            return false;
        
        // get both actors images
        GLTexture img1 = thisActor.getImage();
        GLTexture img2 = otherActor.getImage();
        
        // for counting how many pixels we've checked
        int pixelCount = 0;
        int totalPixelCount = (xMax-xMin)*(yMax-yMin);
        
        // start iterating at the beginning of bounding area,
        int x = xMin;
        int y = yMin;
        
        // incriment yMin to take into account starting on first line,
        // otherwise it will check it a second time
        yMin++;
        
        // the direction the pixels are being checked in,
        // where x or y will increase of decrese
        int direction = 0;
        
        // start iteration
        while (true) {
            // change x and y accordin to which direction were moving
            if (direction == MOVE_RIGHT) {
                x++;
                if (x == xMax) {
                    x--;
                    xMax--;
                    direction = MOVE_DOWN;
                }
            } else if (direction == MOVE_DOWN) {
                y++;
                if (y == yMax) {
                    y--;
                    yMax--;
                    direction = MOVE_LEFT;
                }
            } else if (direction == MOVE_LEFT) {
                x--;
                if (x == xMin) {
                    xMin++;
                    direction = MOVE_UP;
                }
            } else if (direction == MOVE_UP) {
                y--;
                if (y == yMin) {
                    yMin++;
                    direction = MOVE_RIGHT;
                }
            }

            // incriment that were checking another pixel
            pixelCount++;

            // check if pixels at same point on both actors are not transparent
            if (img1.getRGB(x-thisX, y-thisY) != 0 && img2.getRGB(x-otherX, y-otherY) != 0) {
                return true;
            // if we've looked at all pixels
            } else if (pixelCount >= totalPixelCount) {
                return false;
            }
        }
    }
   
    static boolean intersectionImageToShape(ImageActor thisActor, Actor otherActor, Shape otherShape)
    {
        GLTexture img = thisActor.getImage();
        
        // I presume that shape is around the 0x0 point so I transpose the image to match
        int thisX = (int)(thisActor.getX() - otherActor.getX()) - img.getWidth()/2;
        int thisY = (int)(thisActor.getY() - otherActor.getY()) - img.getHeight()/2;
        
        if (otherShape.intersects(thisX, thisY, img.getWidth(), img.getHeight())) {
            
            Rectangle rect = otherShape.getBounds();
            int otherWidth = (int)(rect.getWidth());
            int otherHeight = (int)(rect.getHeight());
            
            int otherX = -otherWidth/2;
            int otherY = -otherHeight/2;
            
            // find the maximum and minimum values of the area which is overlapping
            int xMin = Math.max(thisX , otherX);
            int xMax = Math.min(thisX + thisActor.getWidth(), otherX + otherWidth);
    
            int yMin = Math.max(thisY, otherY);
            int yMax = Math.min(thisY + thisActor.getHeight(), otherY + otherHeight);
            
            // first ensure values of either actors are actually colliding
            if (yMin >= yMax || xMin >= xMax)
                return false;
            
            // for counting how many pixels we've checked
            int pixelCount = 0;
            int totalPixelCount = (xMax-xMin)*(yMax-yMin);
            
            // start iterating at the beginning of bounding area,
            int x = xMin;
            int y = yMin;
            
            // incriment yMin to take into account starting on first line,
            // otherwise it will check it a second time
            yMin++;
            
            // the direction the pixels are being checked in,
            // where x or y will increase of decrese
            int direction = 0;
            
            // start iteration
            while (true) {
                // change x and y accordin to which direction were moving
                if (direction == MOVE_RIGHT) {
                    x++;
                    if (x == xMax) {
                        x--;
                        xMax--;
                        direction = MOVE_DOWN;
                    }
                } else if (direction == MOVE_DOWN) {
                    y++;
                    if (y == yMax) {
                        y--;
                        yMax--;
                        direction = MOVE_LEFT;
                    }
                } else if (direction == MOVE_LEFT) {
                    x--;
                    if (x == xMin) {
                        xMin++;
                        direction = MOVE_UP;
                    }
                } else if (direction == MOVE_UP) {
                    y--;
                    if (y == yMin) {
                        yMin++;
                        direction = MOVE_RIGHT;
                    }
                }
    
                // incriment that were checking another pixel
                pixelCount++;
                
                // check if pixels at same point on both actors are not transparent
                if (otherShape.contains(x, y) && img.getRGB(x-thisX, y-thisY) != 0) {
                    return true;
                // if we've looked at all pixels
                } else if (pixelCount >= totalPixelCount) {
                    return false;
                }
            }
        }
        
        // if all else fails, return false
        return false;
    }
    
    static boolean intersectionShapeToShape(Actor thisActor, Shape thisShape, Actor otherActor, Shape otherShape)
    {
        /* OtherShape is presumed to be at 0x0,
         * though it's actor almost certainly isn't so
         * this shape is moved to a corresponding offset
         * from 0x0. This offset is the difference in
         * positon between thisActor and otherActor.
         * 
         * It's important to remember that otherShape is based
         * around 0x0 and that thisActor is at an offset from 0x0.
         * 
         * Then they are moved again so that their x and y location
         * (thisX, thisX, otherX, otherY) refers to their top left
         * cornet. Why? Cos that's how the images did it.
         */
        Rectangle thisRect = thisShape.getBounds();
        int thisWidth  = (int)(thisRect.getWidth());
        int thisHeight = (int)(thisRect.getHeight());
        
        int differenceX = (int)(thisActor.getX() - otherActor.getX());
        int differenceY = (int)(thisActor.getY() - otherActor.getY());
        // I presume that shape is around the 0x0 point so I transpose the image to match
        int thisX = differenceX - thisWidth/2;
        int thisY = differenceY - thisHeight/2;
        
        if (otherShape.intersects(thisX, thisY, thisWidth, thisHeight)) {
            Rectangle otherRect = otherShape.getBounds();
            int otherWidth = (int)(otherRect.getWidth());
            int otherHeight = (int)(otherRect.getHeight());
            
            int otherX = -otherWidth/2;
            int otherY = -otherHeight/2;
            
            // find the maximum and minimum values of the area which is overlapping
            int xMin = Math.max(thisX , otherX);
            int xMax = Math.min(thisX + thisWidth, otherX + otherWidth);
    
            int yMin = Math.max(thisY , otherY);
            int yMax = Math.min(thisY + thisHeight, otherY + otherHeight);
            
            // first ensure values of either actors are actually colliding
            if (yMin >= yMax || xMin >= xMax) {
                return false;
            }
            
            // for counting how many pixels we've checked
            int pixelCount = 0;
            int totalPixelCount = (xMax-xMin)*(yMax-yMin);
            
            // start iterating at the beginning of bounding area,
            int x = xMin;
            int y = yMin;
            
            // incriment yMin to take into account starting on first line,
            // otherwise it will check it a second time
            yMin++;
            
            // the direction the pixels are being checked in,
            // where x or y will increase of decrese
            int direction = 0;
            
            // start iteration
            while (true) {
                // change x and y accordin to which direction were moving
                if (direction == MOVE_RIGHT) {
                    x++;
                    if (x == xMax) {
                        x--;
                        xMax--;
                        direction = MOVE_DOWN;
                    }
                } else if (direction == MOVE_DOWN) {
                    y++;
                    if (y == yMax) {
                        y--;
                        yMax--;
                        direction = MOVE_LEFT;
                    }
                } else if (direction == MOVE_LEFT) {
                    x--;
                    if (x == xMin) {
                        xMin++;
                        direction = MOVE_UP;
                    }
                } else if (direction == MOVE_UP) {
                    y--;
                    if (y == yMin) {
                        yMin++;
                        direction = MOVE_RIGHT;
                    }
                }
    
                // incriment that were checking another pixel
                pixelCount++;
                
                // check if pixels at same point on both actors are not transparent
                if (thisShape.contains(x - differenceX, y - differenceY) && otherShape.contains(x, y)) {
                    return true;
                // if we've looked at all pixels
                } else if (pixelCount >= totalPixelCount) {
                    return false;
                }
            }
        }
        
        // if all else fails, return false
        return false;
    }
    
    private final Actor actor;
    
    /**
     * Creates a new Intersection and associates it with the given actor.
     * Once set, the actor cannot change.
     * @param actor The Actor that this Intersection works with. Cannot be null.
     */
    protected Intersection(Actor actor)
    {
        if (actor == null) {
            throw new IllegalArgumentException("The actor that this intersection uses cannot be null.");
        }
        
        this.actor = actor;
    }
    
    /**
     * @return The Actor this Intersection is associated with.
     */
    Actor getActor()
    {
        return actor;
    }
     
    /**
     * @return True if this Intersection intersects the given Intersection.
     */
    public abstract boolean isIntersection(Intersection other);
    
    /* When isIntersetion is called the aim is that the sub-class
     * of Intersection calls one of these methods on the given other
     * Intersection. This is so both Intersections can effectively find
     * out what class each of them are and make the appropriate actions. */
    
    /**
     * 
     */
    protected abstract boolean isIntersectionToPixel(PixelIntersection other);
    protected abstract boolean isIntersectionToBounds(BoundsIntersection other);
    protected abstract boolean isIntersectionToImage(ImageIntersection other);
    protected abstract boolean isIntersectionToShape(ShapeIntersection other);
}
 